Younix's Studio.

编程修养

字数统计: 2k阅读时长: 7 min
2016/01/01 Share

[TOC]

初学 C 语言的时候看到过 陈皓《编程修养》 中的内容,当时不以为意。

工作一年后无意间再次看到这篇文章,感触颇深。
发现其中的每一点言之凿凿。

于是拷贝其目录,试试看自己能否也像前辈一样将每一个修养的细节都用代码语言表现出来。

目录

01、版权和版本
02、缩进、空格、换行、空行、对齐
03、程序注释
04、函数的[in][out]参数
05、对系统调用的返回进行判断
06、if 语句对出错的处理
07、头文件中的#ifndef
08、在堆上分配内存
09、变量的初始化
10、h和c文件的使用
11、出错信息的处理
12、常用函数和循环语句中的被计算量
13、函数名和变量名的命名
14、函数的传值和传指针
15、修改别人程序的修养
16、把相同或近乎相同的代码形成函数和宏
17、表达式中的括号
18、函数参数中的const
19、函数的参数个数
20、函数的返回类型,不要省略
21、goto语句的使用
22、宏的使用
23、static的使用
24、函数中的代码尺寸
25、typedef的使用
26、为常量声明宏
27、不要为宏定义加分号
28、||和&&的语句执行顺序
29、尽量用for而不是while做循环
30、请sizeof类型而不是变量
31、不要忽略Warning
32、书写Debug版和Release版的程序

版权&版本

在写代码的时候添加文件头,可以利用 vimrc 自动实现这个功能:Vim设置(非常全)
在每次生成新文件的时候都会自动生成形如下面样式的文件头。

1
2
3
4
5
6
/*************************************************************************
> File Name: test.c
> Author: Younix
> Mail: foreveryounix@gmail.com
> Created Time: 2016年10月14日 星期五 15时28分28秒
************************************************************************/

缩进、空格、换行、空行、对齐

同样利用 Vimrc 来实现,在 .vimrc 中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
" 自动缩进
set autoindent
set cindent
" Tab键的宽度
set tabstop=4
" 统一缩进为4
set softtabstop=4
set shiftwidth=4
" 不要用空格代替制表符
"set noexpandtab
" 将制表符用 4 个空格替代
set ts=4
set expandtab
" 在行和段开始处使用制表符
set smarttab

更多详细的也可以参考之前那篇文章。

BTW:indent 命令也是一个好方法

注释

注释的格式应该如此写:

1
2
3
4
5
6
7
8
9
10
11
/*================================================================
* 函 数 名:XXX
* 参 数:
* type name [IN] : descripts
* 功能描述:
* ..............
* 返 回 值:成功TRUE,失败FALSE
* 抛出异常:
* 作 者:Younix 2016/10/14
*
================================================================*/

函数的[in][out]参数

在调用某个函数前先判断一下传进来的指针是否合法!
使用断言 assert

对系统调用的返回进行判断

  1. 文件返回的文件描述符
  2. socket 返回的 socket 号
  3. malloc 返回的内存
    这些我使用 dbg.h 中的 check() 宏完成检查。
    dbg.h 是 Zed.Shaw 编写的一个比较不错的 debug 宏合集。非常好用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #ifdef NDEBUG
    #define debug(M, ...)
    #else
    #define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__) //牛逼
    #endif
    #define clean_errno() (errno == 0 ? "None" : strerror(errno))
    #define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)
    #define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)
    #define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)

    #define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }
    #define sentinel(M, ...) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }
    #define check_mem(A) check((A), "Out of memory.")
    #define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; }

下载地址:http://download.csdn.net/detail/dearsq/9653980

if 语句的错误处理

先写错误处理,再写正常逻辑

头文件中的 #ifndef

这个毋庸置疑非常重要,为了防止交叉引用

在堆上分配内存

栈上分配内存方法:

1
2
3
4
5
6
char*
AllocStrFromStack()
{
char pstr[100];
return pstr;
}

堆上分配内存方法:

1
2
3
4
5
6
7
8
char*
AllocStrFromHeap(int len)
{
char *pstr;

if ( len <= 0 ) return NULL;
return ( char* ) malloc( len );
}

栈上分配的内存在函数结束时被释放,此时返回有可能出现段错误。
堆上分配的内存在free时才被释放,在函数返回时仍然存在。

malloc 分配的内存一定要初始化;
free 后的指针一定要设置为 NULL。
内存检查工具 Purify

变量初始化

  1. malloc 后记得 memset 清零
  2. 对栈上分配的 struct 或数组进行初始化
  3. 对全局和静态变量,声明时就要初始化

h和c文件的使用

声明和实现严格分开,采用 Makefile 来管理。

另外,在 .h 中不要初始化。

出错信息的处理

我采用的是 dbg.h 中提供的方式。

常用函数和循环语句中的被计算量

1
2
3
4
5
6
7
8
GetLocalHostName(char* name)
{
char funcName[] = "GetLocalHostName";

sys_log( "%s begin......", funcName );
...
sys_log( "%s end......", funcName );
}

如果这个函数经常被调用,那么可以把 funcName 声明为 static。

函数名和变量的命名

这个不多说,最好望文生义。

函数的传值和传指针

其实任何时候都是在传值,看似传指针也只是传的指针的副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void
GetVersion(char* pStr)
{
pStr = malloc(10);
strcpy ( pStr, "2.0" );
}
main()
{
char* ver = NULL;
GetVersion ( ver );
...
...
free ( ver );
}

其实这里函数的形参 pStr 只是 ver 的副本。
因为是副本,pStr 所指向的地方和 ver 所指向的地方相同,但是如果对其取地址的话,可以看到 &pStr 和 &ver 是不同的。因为这里也只是 值 传递。

修改他人程序

删除时请使用注释

把相同或近乎相同的代码形成函数和宏

如题

表达式中的括号

为了避免误解,多加括号没有错的。

函数参数中的 const

在函数中只读的参数请使用 const。
虽然指针即使用 const 修饰后依然能改,但是编译器会报 Warning,可以引起注意。

函数的参数个数

参数太多请用结构体传参。

返回类型

不要省略

goto 语句

除了错误处理,其他情况不要用 goto。

宏定义

宏可以加快运行速度(减少函数调用的开销),但是会增加执行文件体积。
而且注意将变量()起来。

不要给宏定义加分号

static

最初了解 static 是因为全局变量。
但是他最大的作用是在于控制访问。
C 中如果一个函数或者一个全局变量 被声明为 static,那么它们将只能在这个 C 中被访问!

函数代码尺寸

保证函数功能的单一性

typedef

typedef char bool;

为常量声明宏

char name[NAME_SIZE]

|| && 语句执行顺序

注意 && 时,并不一定所有判断都会执行。

尽量用for而不是while做循环

请sizeof 类型而不是变量

不要忽略Warning

  1. 声明了未使用的变量。(虽然编译器不会编译这种变量,但还是把它从源程序中注释或是删除吧)
  2. 使用了隐晦声明的函数。(也许这个函数在别的C文件中,编译时会出现这种警告,你应该这使用之前使用extern关键字声明这个函数)
  3. 没有转换一个指针。(例如malloc返回的指针是void的,你没有把之转成你实际类型而报警,还是手动的在之前明显的转换一下吧)
  4. 类型向下转换。(例如:float f = 2.0; 这种语句是会报警告的,编译会告诉你正试图把一个double转成float,你正在阉割一个变量,你真的要这样做吗?还是在2.0后面加个f吧,不然,2.0就是一个double,而不是float了)

书写Debug版和Release版的程序

参考 dbg.h 添加调试函数。

CATALOG
  1. 1. 目录
  2. 2. 版权&版本
  3. 3. 缩进、空格、换行、空行、对齐
  4. 4. 注释
  5. 5. 函数的[in][out]参数
  6. 6. 对系统调用的返回进行判断
  7. 7. if 语句的错误处理
  8. 8. 头文件中的 #ifndef
  9. 9. 在堆上分配内存
  10. 10. 变量初始化
  11. 11. h和c文件的使用
  12. 12. 出错信息的处理
  13. 13. 常用函数和循环语句中的被计算量
  14. 14. 函数名和变量的命名
  15. 15. 函数的传值和传指针
  16. 16. 修改他人程序
  17. 17. 把相同或近乎相同的代码形成函数和宏
  18. 18. 表达式中的括号
  19. 19. 函数参数中的 const
  20. 20. 函数的参数个数
  21. 21. 返回类型
  22. 22. goto 语句
  23. 23. 宏定义
  24. 24. 不要给宏定义加分号
  25. 25. static
  26. 26. 函数代码尺寸
  27. 27. typedef
  28. 28. 为常量声明宏
  29. 29. || && 语句执行顺序
  30. 30. 尽量用for而不是while做循环
  31. 31. 请sizeof 类型而不是变量
  32. 32. 不要忽略Warning
  33. 33. 书写Debug版和Release版的程序